/**
 * @Created Aug 26, 2010 2:56:32 PM
 * @author r39
 */
package com.philip.journal.core.dao;

import static org.junit.Assert.assertNotNull;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.jdbc.SimpleJdbcTestUtils;
import org.springframework.test.util.ReflectionTestUtils;

import com.philip.journal.core.JournalTestCommon;
import com.philip.journal.core.bean.User;

/**
 * Base class for all DAO unit test class. This will directly read Objects from the database, thus avoiding
 * the dependency to other application DAOs.
 */
@ContextConfiguration(locations = { "/test/config/daoContext.xml" })
public abstract class BaseJournalTestDAO extends AbstractJUnit4SpringContextTests { // NOPMD by r39 on 4/1/11 4:12 PM

    /** Extensible logger. */
    private transient Log logger = LogFactory.getLog(getClass()); // NOPMD by r39 on 3/30/11 6:02 PM

    /** Refactored common functionality. */
    private final JournalTestCommon common = new JournalTestCommon();

    /** SQL to read Entry. */
    static final String SQL_READ_ENTRY = "SELECT * FROM jrnl.ENTRY where ENTRY_ID = ?";

    /** SQL to read Branch. */
    static final String SQL_READ_BRANCH = "SELECT * FROM jrnl.BRANCH where BRANCH_ID = ?";

    /** SQL to read user from DB. */
    private static final String SQL_USER = "SELECT * FROM JRNL.USER WHERE USERNAME=?";

    /** RTFC. */
    private static final String TBL_BRANCH = "jrnl.BRANCH";

    /** RTFC. */
    private static final String TBL_ENTRY = "jrnl.ENTRY";

    /** RTFC. */
    private static final String TBL_CONFIG = "jrnl.CONFIG_ITEM";

    /** Spring context jdbc template for db functionality. */
    private transient SimpleJdbcTemplate jdbcTemplate;

    // Dependent on Test Scripts.
    /** Used on test First Branch ID. */
    protected static final long ID_1ST_TST_BRANCH = 40001L;

    /** Used on test First Entry ID. Used same ID with Branch. */
    protected static final long ID_1ST_TST_ENTRY = ID_1ST_TST_BRANCH;

    /** Used on test of leaf less Branch. */
    protected static final long ID_NOLEAF_BRANCH = 40015L;

    /** Used on test of leafed non-root Branch. */
    protected static final long ID_LEAFED_BRANCH = 40007L;

    /** Used on test of ordered Entry. */
    protected static final long ID_FIRST_ASC = 40010L;

    /** Used on test of ordered Entry. */
    protected static final long ID_FIRST_DESC = 40009L;

    /** Used on test of leafed non-root Branch. Count of leaves. */
    protected static final long CNT_BRANCH_LEAVES = 3;

    /** Non existent in test, Branch ID nor Entry ID. */
    protected static final long ID_NON_EXISTENT = 49094L;

    /** Test Entry count. Available in EntryDAOImplTest.sql */
    protected static final long COUNT_TEST_ENTRY = 10;

    /** RTFC. */
    protected static final int ROOT_BRANCH_COUNT = 3;

    /**
     * Default setup. Will initialize DB state based on SQL scripts derived from the test class name.
     *
     * @throws Exception JUnit3 signature for compatibility.
     */
    @Before
    public void setUp() throws Exception {
        setJdbcTemplate(new SimpleJdbcTemplate((DataSource) applicationContext.getBean("dataSource")));

        SimpleJdbcTestUtils.executeSqlScript(getJdbcTemplate(), new ClassPathResource(
                "/test/config/CreateSchema.sql"), false);
        SimpleJdbcTestUtils.executeSqlScript(getJdbcTemplate(), new ClassPathResource(
                "/test/config/CreateData.sql"), false);

        final String testSqlPath = getTestSqlPath();
        if (testSqlPath != null) {
            try {
                LogFactory.getLog(BaseJournalTestDAO.class).info("Running script: " + testSqlPath);
                SimpleJdbcTestUtils.executeSqlScript(getJdbcTemplate(), new ClassPathResource(testSqlPath),
                        false);
            } catch (final DataAccessResourceFailureException darf) {
                LogFactory.getLog(BaseJournalTestDAO.class).warn(
                        "SQL script for " + getClass().getSimpleName() + " was not found.");
            }
        }
    }

    /**
     *
     * @throws Exception JUnit3 signature for compatibility.
     */
    @After
    public void tearDown() throws Exception {
        SimpleJdbcTestUtils.executeSqlScript(getJdbcTemplate(), new ClassPathResource(
                "/test/config/DeleteSchema.sql"), false);
    }

    protected String getTestSqlPath() {
        return "/" + getClass().getName().replaceAll("\\.", "/") + ".sql";
    }

    /**
     * This requires the sub class to supply the DAO implementation under test. This will allow this class to
     * test common testable parts of the object under test.
     *
     * @return Target DAO Impl to test.
     */
    protected abstract Object getTargetDAOImpl();

    /**
     * Generic verification that entity is defined for the DAO.
     */
    @Test
    public void testGetTargetClass() {
        assertNotNull("DAO under test must return not null on getTargetClass call.",
                ReflectionTestUtils.invokeGetterMethod(getTargetDAOImpl(), "targetClass"));
    }

    /**
     * Directly read a record in the database in form of a Map.
     *
     * @param entryId Entry ID to read.
     * @return Map representation of the Entity.
     */
    protected Map<String, Object> readEntry(final long entryId) {
        Map<String, Object> retval = null;
        try {
            retval = getJdbcTemplate().queryForMap(SQL_READ_ENTRY, new Object[] { Long.valueOf(entryId) });
        } catch (final DataAccessException nodatafound) {
            Logger.getLogger(BaseJournalTestDAO.class).error(nodatafound.getMessage(), nodatafound);
        }
        return retval;
    }

    /**
     * Returns a column_name to value map of the entity.
     *
     * @param branchId Branch ID.
     * @return the Map representation of the Branch,
     */
    protected Map<String, Object> readBranch(final long branchId) {
        Map<String, Object> retval = null;
        try {
            retval = getJdbcTemplate().queryForMap(SQL_READ_BRANCH, new Object[] { Long.valueOf(branchId) });
        } catch (final DataAccessException nodatafound) {
            Logger.getLogger(BaseJournalTestDAO.class).warn("Test data read yields no result.", nodatafound);
        }
        return retval;
    }

    /**
     * Returns all branch in the test DB environment.
     *
     * @return all branch read from the test DB.
     */
    protected List<Map<String, Object>> readAllBranch() {
        return getJdbcTemplate().queryForList("SELECT * FROM jrnl.BRANCH", new Object[0]);
    }

    /**
     * Clears the Entry table.
     */
    protected void clearEntryTable() {
        getJdbcTemplate().update("delete from jrnl.ENTRY", new Object[0]);
    }

    /**
     * Will return the test User for use by sub classes.
     *
     * @return the test user.
     */
    protected User getTestUser() {
        return getJdbcTemplate().queryForObject(SQL_USER, new RowMapper<User>() {
            @Override
            public User mapRow(final ResultSet resultSet, final int rowNum) throws SQLException {
                final User user = new User(resultSet.getString("USERNAME"));
                user.setPassword(resultSet.getString("PASSWORD"));
                user.setChecksum(resultSet.getString("checksum"));
                user.setCreateDate(resultSet.getDate("create_date"));
                user.setCreateTime(resultSet.getTime("create_time"));
                user.setUpdateDate(resultSet.getDate("update_date"));
                user.setUpdateTime(resultSet.getTime("update_time"));
                return user;
            }
        }, new Object[] { JournalTestCommon.TEST_USERNAME });
    }

    /**
     * Helper method to getRecord count of a table.
     *
     * @param tableName table name.
     * @return count of records in the table.
     */
    protected int getRecordCount(final String tableName) {
        final String query = "SELECT count(*) FROM " + tableName;
        return getJdbcTemplate().queryForInt(query);
    }

    /** @return the logger. */
    public Log getLogger()
    {
        return logger;
    }

    /**
     * @return the branchTableName
     */
    public String getBranchTableName() {
        return TBL_BRANCH;
    }

    /**
     * @return the entryTableName
     */
    public String getEntryTableName() {
        return TBL_ENTRY;
    }

    /**
     * @return the configTableName
     */
    public String getConfigTableName() {
        return TBL_CONFIG;
    }

    /** @return the jdbcTemplate. */
    public SimpleJdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    /** @param pJdbcTemplate the jdbcTemplate to set. */
    public void setJdbcTemplate(final SimpleJdbcTemplate pJdbcTemplate) {
        this.jdbcTemplate = pJdbcTemplate;
    }

    protected JournalTestCommon getCommon() {
        return common;
    }

}
